﻿// (c) Copyright 2013 Mike Riedel.
// This source is subject to the Microsoft Public License (Ms-PL).
// All other rights reserved.

using System;
using System.IO;
using System.Linq;
using FreshMeat.Storage;
#if NETFX_CORE || WINDOWS_PHONE
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#else
using Microsoft.VisualStudio.TestTools.UnitTesting;
#endif

namespace Storage.More.Test
{
   [TestClass]
   public abstract class CommonFileSystemTest
   {
      [TestMethod]
      public void CreatesFolder()
      {
         var system = CreateSystem();

         system.CreateFolder("folder1");

         var exists = system.FolderExists("folder1");
         Assert.IsTrue(exists, "Folder should exist after being created.");
      }

      [TestMethod]
      public void CreatesFolderWithEmptyPath()
      {
         var system = CreateSystem();

         system.CreateFolder("");

         var exists = system.FolderExists("");
         Assert.IsTrue(exists, "Folder should exist after being created.");
      }

      [TestMethod]
      public void CreatesNestedFolders()
      {
         var system = CreateSystem();

         system.CreateFolder("folder1\\folder2\\folder3");

         var exists1 = system.FolderExists("folder1");
         Assert.IsTrue(exists1, "folder1 should exist after being created.");
         var exists2 = system.FolderExists("folder1\\folder2");
         Assert.IsTrue(exists2, "folder2 should exist after being created.");
         var exists3 = system.FolderExists("folder1\\folder2\\folder3");
         Assert.IsTrue(exists3, "folder3 should exist after being created.");
      }

      [TestMethod]
      public void FolderExistsReturnsTrue()
      {
         var system = CreateSystem();
         system.CreateFolder("folder1");

         var exists = system.FolderExists("folder1");

         Assert.IsTrue(exists, "Folder does exist.");
      }

      [TestMethod]
      public void FolderExistsReturnsFalse()
      {
         var system = CreateSystem();

         var exists = system.FolderExists("NonExistingFolder");

         Assert.IsFalse(exists, "Folder does not exist.");
      }

      [TestMethod]
      public void DeleteExistingFolder()
      {
         var system = CreateSystem();
         system.CreateFolder("folder1");

         var existed = system.DeleteFolder("folder1");

         Assert.IsTrue(existed, "Folder existed before being deleted.");
      }

      [TestMethod]
      public void DeleteNonExistingFolder()
      {
         var system = CreateSystem();

         var existed = system.DeleteFolder("folder1");

         Assert.IsFalse(existed, "Folder did not exist.");
      }

      [TestMethod]
      public void DeletedFolderNoLongExists()
      {
         var system = CreateSystem();
         system.CreateFolder("folder1");
         system.DeleteFolder("folder1");

         var exists = system.FolderExists("folder1");

         Assert.IsFalse(exists, "Folder should no longer exist after being deleted.");
      }

      [TestMethod]
      public void FileExistsReturnsTrue()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");

         var exists = system.FileExists("file1.txt");

         Assert.IsTrue(exists, "File does exist.");
      }

      [TestMethod]
      public void FileExistsReturnsFalse()
      {
         var system = CreateSystem();

         var exists = system.FileExists("NonExistingFile.txt");

         Assert.IsFalse(exists, "File does not exist.");
      }

      [TestMethod]
      public void DeleteExistingFile()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");

         var existed = system.DeleteFile("file1.txt");

         Assert.IsTrue(existed, "File existed before being deleted.");
      }

      [TestMethod]
      public void DeleteNonExistingFile()
      {
         var system = CreateSystem();

         var existed = system.DeleteFile("file1.txt");

         Assert.IsFalse(existed, "File did not exist.");
      }

      [TestMethod]
      public void DeletedFileNoLongExists()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.DeleteFile("file1.txt");

         var exists = system.FileExists("file1.txt");

         Assert.IsFalse(exists, "File should no longer exist after being deleted.");
      }

      [TestMethod]
      public void EnumerateFilesNonRecursive()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.WriteAllText("file2.txt", "abc");
         system.CreateFolder("sub1");
         system.WriteAllText("sub1\\file1.txt", "abc");
         system.WriteAllText("sub1\\file2.txt", "abc");
         system.CreateFolder("sub2");
         system.WriteAllText("sub2\\file1.txt", "abc");
         system.WriteAllText("sub2\\file2.txt", "abc");
         system.CreateFolder("sub1\\sub2");
         system.WriteAllText("sub1\\sub2\\file1.txt", "abc");
         system.WriteAllText("sub1\\sub2\\file2.txt", "abc");

         var files = system.EnumerateFiles("", "*.txt", false).OrderBy(path => path).ToArray();

         Assert.AreEqual(2, files.Length, "Wrong number of files found.");
         Assert.AreEqual("file1.txt", files[0]);
         Assert.AreEqual("file2.txt", files[1]);
      }

      [TestMethod]
      public void EnumerateFilesRecursively()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.WriteAllText("file2.txt", "abc");
         system.CreateFolder("sub1");
         system.WriteAllText("sub1\\file1.txt", "abc");
         system.WriteAllText("sub1\\file2.txt", "abc");
         system.CreateFolder("sub1\\sub2");
         system.WriteAllText("sub1\\sub2\\file1.txt", "abc");
         system.WriteAllText("sub1\\sub2\\file2.txt", "abc");
         system.CreateFolder("sub2");
         system.WriteAllText("sub2\\file1.txt", "abc");
         system.WriteAllText("sub2\\file2.txt", "abc");

         var files = system.EnumerateFiles("", "*.txt", true).OrderBy(path => path).ToArray();

         Assert.AreEqual(8, files.Length, "Wrong number of files found.");
         Assert.AreEqual("file1.txt", files[0]);
         Assert.AreEqual("file2.txt", files[1]);
         Assert.AreEqual("sub1\\file1.txt", files[2]);
         Assert.AreEqual("sub1\\file2.txt", files[3]);
         Assert.AreEqual("sub1\\sub2\\file1.txt", files[4]);
         Assert.AreEqual("sub1\\sub2\\file2.txt", files[5]);
         Assert.AreEqual("sub2\\file1.txt", files[6]);
         Assert.AreEqual("sub2\\file2.txt", files[7]);
      }

      [TestMethod]
      public void MoveFileWhenDestinationExistsThrows()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.CreateFolder("folder1");
         system.WriteAllText("folder1\\file1.txt", "abc");

         AssertHelper.ThrowsException<InvalidOperationException>(
            () => system.MoveFile("file1.txt", "folder1\\file1.txt"), 
            false,
            message: "Should have thrown because destination already existed.");
      }

      [TestMethod]
      public void MoveFileWhenSourceDoesntExistThrows()
      {
         var system = CreateSystem();
         system.CreateFolder("folder1");
         system.WriteAllText("folder1\\file1.txt", "abc");

         AssertHelper.ThrowsException<InvalidOperationException>(
            () => system.MoveFile("file1.txt", "folder1\\file1.txt"), 
            false,
            message: "Should have thrown because source did not exist.");
      }

      [TestMethod]
      public void MoveWithoutRenameWorks()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.CreateFolder("folder1");

         system.MoveFile("file1.txt", "folder1\\file1.txt");

         Assert.IsFalse(system.FileExists("file1.txt"), "Source should no longer exist.");
         Assert.IsTrue(system.FileExists("folder1\\file1.txt"), "Destination should exist.");
      }

      [TestMethod]
      public void MoveWithRenameWorks()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.CreateFolder("folder1");

         system.MoveFile("file1.txt", "folder1\\file2.txt");

         Assert.IsFalse(system.FileExists("file1.txt"), "Source should no longer exist.");
         Assert.IsTrue(system.FileExists("folder1\\file2.txt"), "Destination with new name should exist.");
      }

      [TestMethod]
      public void MoveFileToItselfDoesNothing()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");
         system.CreateFolder("folder1");

         system.MoveFile("file1.txt", "file1.txt");

         var exists = system.FileExists("file1.txt");
         Assert.IsTrue(exists, "Source should still exist.");
      }

      [TestMethod]
      public void WritesAllTextCreatesFile()
      {
         var system = CreateSystem();

         system.WriteAllText("file1.txt", "abc");

         var exists = system.FileExists("file1.txt");
         Assert.IsTrue(exists, "File should exist after being created.");
      }

      [TestMethod]
      public void WritesAndReadsAllText()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");

         var contents = system.ReadAllText("file1.txt");

         Assert.AreEqual("abc", contents, "Wrong data was either written or read.");
      }

      [TestMethod]
      public void WritesAllTextOverwritesFile()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "abc");

         system.WriteAllText("file1.txt", "xyz"); // should overwrite existing file

         var contents = system.ReadAllText("file1.txt");
         Assert.AreEqual("xyz", contents, "Wrong data was either written or read.");
      }

      [TestMethod]
      public void OpenForWriteCreatesFile()
      {
         var system = CreateSystem();

         using (var writeStream = system.OpenForWrite("file1.txt"))
         using (var writer = new StreamWriter(writeStream))
            writer.Write("abc");

         var exists = system.FileExists("file1.txt");
         Assert.IsTrue(exists, "File should exist after writing to it.");
      }

      [TestMethod]
      public void OpenForWriteOverwritesFile()
      {
         var system = CreateSystem();
         system.WriteAllText("file1.txt", "already exists");

         using (var writeStream = system.OpenForWrite("file1.txt"))
         using (var writer = new StreamWriter(writeStream))
            writer.Write("abc");

         var contents = system.ReadAllText("file1.txt");
         Assert.AreEqual("abceady exists", contents, "File contents should have been partially overwritten by new text.");
      }

      [TestMethod]
      public void OpenForWritePreventsAnotherReader()
      {
         var system = CreateSystem();
         using (var writeStream = system.OpenForWrite("file1.txt"))
         using (new StreamWriter(writeStream))
         {

            // Exception is least-common-denominator between platforms for this situation
            AssertHelper.ThrowsException<Exception>(() => system.ReadAllText("file1.txt"), true);
         }
      }

      [TestMethod]
      public void OpenForWritePreventsAnotherWriter()
      {
         var system = CreateSystem();
         using (var writeStream = system.OpenForWrite("file1.txt"))
         using (new StreamWriter(writeStream))
         {

            // Exception is least-common-denominator between platforms for this situation
            AssertHelper.ThrowsException<Exception>(() => system.WriteAllText("file1.txt", "lkajsdlkj"), true);
         }
      }

      /// <summary>
      /// When implemented in a derived class, returns a fresh IFileSystem instance.
      /// </summary>
      /// <returns></returns>
      protected abstract IFileSystem CreateSystem();
   }
}